package com.urick.loadbanlance.dubbo;

import cn.hutool.core.lang.Console;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Design Theories:
 * <p>
 * 权重，轮询负载均衡会根据设置的权重来判断轮询的比例。
 * 普通轮询负载均衡的好处是每个节点获得的请求会很均匀， 如果某些节点的负载能力明显较弱， 则这个节点会堆积比较多的请求。
 * 因此普通的轮询还不能满足需求， 还需要能根据节点权重进行干预。 权重轮询又分为普通权重轮询和平滑权重轮询。
 * 普通权重轮询会造成某个节点会突然被频繁选中， 这样很容易突然让一个节点流量暴增。
 * Nginx中有一种叫平滑轮询的算法(smooth weighted round-robin balancing),这种算法在轮询时会穿插选择其他节点， 让整个服务器选择的过程比较均匀，
 * 不会 “逮住” 一个节点一直调用Dubbo框架中最新的RoundRobin代码已经改为平滑权重轮询算法，轮询过程中穿插服务调用
 * <a href=""></a>
 *
 * @author uRick 2021/12/13 18:18
 * Email: rickwork@163.com
 * Wechat: yy3076815642
 * QQ: 3076815642
 * @since 1.0
 */
public class RoundRobinLoadBalance extends AbstractLoadBalance {
    private static final int RECYCLE_PERIOD = 60000;

    protected static class WeightedRoundRobin {
        private int weight;
        private AtomicLong current = new AtomicLong(0);
        private long lastUpdate;

        public int getWeight() {
            return weight;
        }

        public void setWeight(int weight) {
            this.weight = weight;
            current.set(0);
        }

        public long increaseCurrent() {
            long l = current.addAndGet(weight);
            Console.log("{}", l);
            return l;
        }

        public void sel(int total) {
            long l = current.addAndGet(-1 * total);
            Console.log("{}", l);
        }

        public long getLastUpdate() {
            return lastUpdate;
        }

        public void setLastUpdate(long lastUpdate) {
            this.lastUpdate = lastUpdate;
        }
    }

    private ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<String, ConcurrentMap<String, WeightedRoundRobin>>();

    /**
     * get invoker addr list cached for specified invocation
     * <p>
     * <b>for unit test only</b>
     *
     * @param instanceList
     * @return
     */
    protected <T> Collection<String> getInvokerAddrList(List<ServerInstance> instanceList) {
        String key = instanceList.get(0).getUid();
        Map<String, WeightedRoundRobin> map = methodWeightMap.get(key);
        if (map != null) {
            return map.keySet();
        }
        return null;
    }

    @Override
    protected ServerInstance doSelect(List<ServerInstance> instanceList) {
        String key = instanceList.get(0).getUid();
        ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.computeIfAbsent(key, k -> new ConcurrentHashMap<>());
        int totalWeight = 0;//总权重
        long maxCurrent = Long.MIN_VALUE;
        long now = System.currentTimeMillis();
        ServerInstance selectedInvoker = null;//选中的实例
        WeightedRoundRobin selectedWRR = null;//选中的权重实例
        for (ServerInstance instance : instanceList) {
            String identifyString = instance.toIdentityString();
            int weight = getWeight(instance, Boolean.FALSE);
            WeightedRoundRobin weightedRoundRobin = map.computeIfAbsent(identifyString, k -> {
                WeightedRoundRobin wrr = new WeightedRoundRobin();
                wrr.setWeight(weight);
                return wrr;
            });

            if (weight != weightedRoundRobin.getWeight()) {
                //weight changed
                weightedRoundRobin.setWeight(weight);
            }
            long cur = weightedRoundRobin.increaseCurrent();//当前权重继续叠加求和
            weightedRoundRobin.setLastUpdate(now);//更新时间
            if (cur > maxCurrent) {//当前权重大于最大权重时，选中实例
                maxCurrent = cur;
                selectedInvoker = instance;
                selectedWRR = weightedRoundRobin;
            }
            totalWeight += weight;
        }

        if (instanceList.size() != map.size()) {
            map.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > RECYCLE_PERIOD);
        }
        if (selectedInvoker != null) {
            selectedWRR.sel(totalWeight);//current-totalWeight
            return selectedInvoker;
        }
        // should not happen here
        return instanceList.get(0);
    }
}
